<?php

/**
 * uniBoard Forum Session Class
 *
 * Light and fast forum solution for web 2.0-like communities.
 *
 * @package		uniBoard
 * @author		Rafał Pitoń
 * @license		http://www.gnu.org/licenses/gpl-3.0.html
 * @since		Version 0.1
 * 
 * ------------------------------------------------------------------------
 * 
 * $Date: 2010-09-20 21:23:27 +0000 (Mon, 20 Sep 2010) $
 * $Revision: 64 $
 * $Author: rafio.xudb@gmail.com $
 */

// ------------------------------------------------------------------------

class class_session{
		
	/**
	 * Main class pointer
	 *
	 * @var object
	 */
	
	private $appCore		= NULL;
	
	/**
	 * 32 chars long session id
	 *
	 * @var string
	 */
	
	public $session_id		= NULL;
	
	/**
	 * 6 chars long session key used to verify request
	 *
	 * @var string
	 */
	
	public $session_key		= NULL;
	
	/**
	 * 8 chars long session code used to verify request
	 *
	 * @var string
	 */
	
	public $session_code	= NULL;
	
	/**
	 * If session belongs to BOT, bot id will be bigger than 0
	 *
	 * @var integer
	 */
	
	public $session_bot		= 0;
	
	/**
	 * Session error number
	 * 
	 * 0 - No error
	 * 1 - No session
	 * 2 - Timed out session
	 * 3 - Banned session
	 *
	 * @var integer
	 */
	
	public $session_error	= 0;
	
	// ------------------------------------------------------------------------

	/**
	 * Initialise Session class
	 *
	 * @param object $appCore
	 */
	
	function __construct( $appCore){
	
		// Store appCore pointer
		$this -> appCore = $appCore;
		
		// Collect session id from cookie
		if ( IN_ADMIN)
		{
			// Cookie with admin session id
			$this -> session_id = $this -> appCore -> db -> strEscape( $this -> appCore -> _COOKIE( 'adm_session_id'));
		}
		else
		{
			// Find out if we are Spider bot? Spider bots behave differently to humans, and thus they need special care
			$this -> identifyBot();
			
			// Bot identified?
			if ( $this -> session_bot > 0)
			{
				// Get Bot session
				$this -> getBotSession();
			}
			else
			{
				// Cookie with regular session id
				$this -> session_id = $this -> appCore -> db -> strEscape( $this -> appCore -> _COOKIE( 'session_id'));
			}
		}
		
		// Perhaps session id is in query string?
		if ( SID_FROM_QUERY || !isset( $this -> session_id[31]))
		{
			// Grab "SID" from query string
			$this -> session_id = $this -> appCore -> db -> strEscape( $this -> appCore -> _GET( 'sid'));
			
			// Tell main class to put SID into urls
			$this -> appCore -> sid_url = true;
		}

		// We are not in Admin, detect IP ban
		if ( !IN_ADMIN)
		{
			$appCore -> banned_ip = $appCore -> checkBan( $appCore -> client_ip, false, false, true);
		}
			
		// Is session ID lenght a correct one?
		if ( isset( $this -> session_id[31]))
		{
			// Get session
			$this -> getSession();
		}
		else
		{
			// We are in Admin? If so, create basic session data
			if ( IN_ADMIN)
			{
				// Set some basic data in main clas
				$this -> appCore -> user = array(
					'id' => -1,
					'language' => $this -> appCore -> conf['default_language']
				);
			}
			else
			{
				// We dont use guest sessions in Admin. If we are bot, we gonna open bot session:
				if ( $this -> session_bot > 0)
				{
					// Start bot session
					$this -> newBotSession();
				}
				else
				{
					// We gonna check if our IP is banned
					// If we are not banned, and autolog cookies exists, lead us in
					if ( $appCore -> conf['board_autolog_allow'] && !$appCore -> banned_ip)
					{
						$this -> tryAutoLog();
					}
					
					// If core user array is empty, we still dont have any session
					if ( empty( $appCore -> user))
					{
						// Start new guest session
						$this -> newSession( -1);
					}
				}
			}
		}
	
	}
	
	// ------------------------------------------------------------------------

	/**
	 * Identifies Web Crawler
	 *
	 */
	
	function identifyBot(){
		
		// Load Crawlers from cache
		$crawlers = (array) class_cache::loadCache( 'crawlers');
		
		// Cache not loaded?
		if ( class_cache::$load_error)
		{
			// Empty Array
			$crawlers = array();
			
			// Select Crawlers
			$this -> appCore -> db -> query( 'SELECT crawler_id, crawler_agent
			FROM ' . DB_PREFIX . 'crawlers
			ORDER BY crawler_agent ASC');
			
			// Parse result
			while ( $crawler_result = $this -> appCore -> db -> fetch_array())
			{
				// Crawler ID and Agent
				$crawlers[] = array( $crawler_result[0], $crawler_result[1]);
			}
			
			// Save Cache
			class_cache::saveCache( 'crawlers', $crawlers);
		}
		
		// Identify bot?
		if ( isset( $crawlers[0]))
		{
			// Crawlers number
			$crawlers_n = count( $crawlers);
			
			// Reiterate
			for ( $i = 0; $i < $crawlers_n; $i ++)
			{
				// Crawler match?
				if ( strpos( $this -> appCore -> client_agent, $crawlers[$i][1]) !== false)
				{
					// Set Crawler Id
					$this -> session_bot = $crawlers[$i][0];
					
					// Stop Entire Function
					return;
				}
			}
		}
		
	}
	
	// ------------------------------------------------------------------------

	/**
	 * Grab session from database
	 *
	 */
	
	function getSession(){
		
		// Select session
		if ( IN_ADMIN)
		{
			// Admin session
			$this -> appCore -> db -> query( 'SELECT s.*, u.user_id, u.user_login, u.user_name, u.user_timezone, u.user_dst, u.user_groups, u.user_masks, u.user_language, l.language_iso, u.user_acl_cache
			FROM ' . DB_PREFIX . 'sessions_admin s
			LEFT JOIN ' . DB_PREFIX . 'users u ON s.user_id = u.user_id
			LEFT JOIN ' . DB_PREFIX . 'languages l ON u.user_language = l.language_id
			WHERE s.session_id = \'' . $this -> session_id . '\'' . ( $this -> appCore -> conf['session_auth_ip'] ? ' AND s.session_ip = \'' . $this -> appCore -> client_ip . '\'' : '') . ( $this -> appCore -> conf['session_auth_agent'] ? ' AND s.session_agent = \'' . $this -> appCore -> db -> strEscape( $this -> appCore -> client_agent) . '\'' : '') . '
			LIMIT 1');
		}
		else
		{
			// Frontend session
			$this -> appCore -> db -> query( 'SELECT s.*, u.user_id, u.user_login, u.user_name, u.user_email, u.user_last_date, u.user_last_ip, u.user_posts, u.user_last_post, u.user_last_search, u.user_rank, u.user_title, g.group_rank, u.user_avatar_type, u.user_avatar_image, u.user_avatar_width, u.user_avatar_height, u.user_timezone, u.user_dst, u.user_groups, u.user_masks, u.user_language, l.language_iso, u.user_style, u.user_acl_cache, i.imageset_id
			FROM ' . DB_PREFIX . 'sessions s
			LEFT JOIN ' . DB_PREFIX . 'users u ON s.user_id = u.user_id
			LEFT JOIN ' . DB_PREFIX . 'user_groups g ON g.group_id = u.group_id
			LEFT JOIN ' . DB_PREFIX . 'languages l ON u.user_language = l.language_id
			LEFT JOIN ' . DB_PREFIX . 'styles i ON u.user_style = i.style_id
			WHERE s.session_id = \'' . $this -> session_id . '\'' . ( $this -> appCore -> conf['session_auth_ip'] ? ' AND s.session_ip = \'' . $this -> appCore -> client_ip . '\'' : '') . ( $this -> appCore -> conf['session_auth_agent'] ? ' AND s.session_agent = \'' . $this -> appCore -> db -> strEscape( $this -> appCore -> client_agent) . '\'' : '') . '
			LIMIT 1');
		}
		
		// Result was found?
		if ( $session_result = $this -> appCore -> db -> fetch_assoc())
		{
			// Free result
			$this -> appCore -> db -> freeResult();
			
			// User has Permissions?
			if (isset( $session_result['user_acl_cache'][0]))
			{
				// Load Cache
				$this -> appCore -> user_acl = (array) unserialize( $session_result['user_acl_cache']);
			}
			else
			{
				// Start ACL builder
				$acl_builder = new class_acl_builder( $this -> appCore);
				
				// Make Perms
				$this -> appCore -> user_acl = $acl_builder -> makeACL( explode( ',', $session_result['user_groups']), explode( ',', $session_result['user_masks']));
				
				// Update Users ACL
				$this -> appCore -> db -> update( array( 'user_acl_cache' => $this -> appCore -> db -> strEscape( serialize( $this -> appCore -> user_acl))), 'users', 'user_id = \'' . $session_result['user_id'] . '\'');
				
				// Kill builder
				unset( $acl_builder);
			}
			
			if ( IN_ADMIN && ( $this -> appCore -> _GET( $session_result['session_key']) != $session_result['session_code'] || !isset( $session_result['session_code'][0])))
			{
				// Pretend its allright, but handle it as guest
				$this -> session_error = 0;
				$this -> appCore -> user = array(
					'id' => -1,
					'language' => $this -> appCore -> conf['default_language']
				);
			}
			else if ( $session_result['session_last_active'] < $this -> appCore -> time - $this -> appCore -> conf['session_auth_timeout'])
			{
				// Session has timed out
				$this -> session_error = 2;	
			}
			else if ( IN_ADMIN && !$this -> appCore -> user_acl['is_admin'])
			{
				// Correct ADMIN session not found
				$this -> session_error = 1;
			}
			else if ( !IN_ADMIN && $this -> appCore -> checkBan( $session_result['user_email'], false, true))
			{
				// Session banned so it cant exsist
				$this -> session_error = 1;
			}
			else
			{
				// Set different session data basing on session type
				if ( IN_ADMIN)
				{
					// We dont need much stuff in ACP
					$this -> appCore -> user = array(
						'id' 				=> $session_result['user_id'],
						'name' 				=> $session_result['user_name'],
						'language' 			=> $session_result['user_language'],
						'language_iso' 		=> $session_result['language_iso'],
						'groups'			=> explode( ',', $session_result['user_groups']),
						'masks' 			=> explode( ',', $session_result['user_masks']),
					);
				}
				else
				{
					// Forum needs much more data than ACP
					$this -> appCore -> user = array(
						'id' 				=> $session_result['user_id'],
						'login' 			=> $session_result['user_login'],
						'name' 				=> $session_result['user_name'],
						'last_date'			=> $session_result['user_last_date'],
						'last_ip'			=> $session_result['user_last_ip'],
						'posts'				=> $session_result['user_posts'],
						'user_rank'			=> $session_result['user_rank'],
						'group_rank'		=> $session_result['group_rank'],
						'title'				=> $session_result['user_title'],
						'last_post'			=> $session_result['user_last_post'],
						'last_search'		=> $session_result['user_last_search'],
						'avatar_type'		=> $session_result['user_avatar_type'],
						'avatar_image'		=> $session_result['user_avatar_image'],
						'avatar_width'		=> $session_result['user_avatar_width'],
						'avatar_height'		=> $session_result['user_avatar_height'],
						'groups'			=> explode( ',', $session_result['user_groups']),
						'masks' 			=> explode( ',', $session_result['user_masks']),
						'language' 			=> $session_result['user_language'],
						'language_iso' 		=> $session_result['language_iso'],
						'style' 			=> $session_result['user_style'],
						'imageset' 			=> $session_result['imageset_id']
					);
				}
						
				// Set user-specific stuff
				if ( $session_result['user_id'] > 0)
				{
					// User Time
					$this -> appCore -> time_zone = 3600 * $session_result['user_timezone'];
					$this -> appCore -> time_dst = $session_result['user_dst'];
				}
				else
				{
					// Default time
					$this -> appCore -> time_zone = 3600 * $this -> appCore -> conf['board_timezone'];
					$this -> appCore -> time_dst = $this -> appCore -> conf['board_use_dst'];
				}
				
				// Store KEY-CODE
				$this -> session_key = $session_result['session_key'];
				$this -> session_code = $session_result['session_code'];
				
				// Update non-admin Session
				if ( !IN_ADMIN)
				{
					// Perform update
					$this -> appCore -> db -> update( array(
						'session_last_active' 		=> $this -> appCore -> time,
						'session_action' 			=> $this -> appCore -> db -> strEscape( $this -> appCore -> _REQ( 'act')),
						'session_forum' 			=> ( !is_array( $this -> appCore -> _REQ( 'forum')) ? (integer) $this -> appCore -> _REQ( 'forum') : 0),
						'session_topic' 			=> ( !is_array( $this -> appCore -> _REQ( 'topic')) ? (integer) $this -> appCore -> _REQ( 'topic') : 0),
						'session_user' 				=> ( !is_array( $this -> appCore -> _REQ( 'user')) ? (integer) $this -> appCore -> _REQ( 'user') : 0)
					), 'sessions', 'session_id = \'' . $this -> session_id . '\'');
					
					// Refresh cookie
					$this -> appCore -> setCookie( 'session_id', $this -> session_id);
				}
				
				// Session All-right
				$this -> session_error = 0;	
			}	
		}
		else
		{
			// Session not found
			$this -> session_error = 1;
		}
		
		// Handle error
		if ( $this -> session_error != 0)
		{
			// What should we do?
			if ( IN_ADMIN)
			{
				// Destroy session
				$this -> destroyAdminSession( $this -> session_error > 1);
			}
			else
			{
				// Attempt autolog us in
				if ( $this -> appCore -> conf['board_autolog_allow'] && !$this -> appCore -> banned_ip)
				{
					$this -> tryAutoLog();
				}
				
				// If core user array is empty, we still dont have any session
				if ( empty( $this -> appCore -> user))
				{
					// Start new guest session
					$this -> newSession( -1);
				}
			}
		}
		
	}
	
	// ------------------------------------------------------------------------

	/**
	 * Attempts to open new session with autolog-key
	 *
	 * @param integer $user_id
	 */
	
	function tryAutoLog(){
		
		// Grab Raw cookie
		$raw_cookie = $this -> appCore -> _COOKIE( 'session_key');
		
		// Read User ID
		$user_id = (int) substr( $raw_cookie, 0, strpos( $raw_cookie, ':'));
		
		// Read Key
		$session_key = substr( $raw_cookie, 1 + strpos( $raw_cookie, ':'));
		
		// Both ID and Session Key are allright?
		if ( $user_id > 0 && isset( $session_key[23]) && !isset( $session_key[24]))
		{
			// Validate Autolog key
			$this -> appCore -> db -> query( 'SELECT * FROM ' . DB_PREFIX . 'sessions_keys WHERE user_id = \'' . $user_id . '\' AND key_id = \'' . $this -> appCore -> db -> strEscape( $session_key) . '\'' . ( $this -> appCore -> conf['board_autolog_lenght'] > 0 ? ' AND key_last_use > \'' . ($this -> appCore -> time - (86400 * $this -> appCore -> conf['board_autolog_lenght'])) . '\'' : '') . ' LIMIT 1');
			
			// Got it?
			if ( $key_result = $this -> appCore -> db -> fetch_array())
			{
				// Generate new Key
				$new_key = $this -> appCore -> makeSalt( 24, false);
							
				// Update current key
				$this -> appCore -> db -> update( array(
					'key_id'			=> $new_key,
					'key_last_use'		=> $this -> appCore -> time,
				), 'sessions_keys', 'key_id = \'' . $this -> appCore -> db -> strEscape( $session_key) . '\'');
				
				// Set Cookie
				$this -> appCore -> setCookie( 'session_key', $user_id . ':' . $new_key, true);
				
				// Free result
				$this -> appCore -> db -> freeResult();
				
				// New user
				$this -> newSession( $key_result[1], $key_result[3]);
			}
			else
			{
				// Simply free result
				$this -> appCore -> db -> freeResult();
			}
		}
		
	}
	
	// ------------------------------------------------------------------------

	/**
	 * Start new session
	 *
	 * @param integer $user_id
	 */
	
	function newSession( $user_id = -1){
		
		// Generate session id
		$this -> session_id = md5( mt_rand( 0, 10) . time() . mt_rand( 0, 10) . $this -> appCore -> client_ip . mt_rand( 0, 10));
		
		// Generate session key and code
		$this -> session_key = $this -> appCore -> makeSalt( 6, false);
		$this -> session_code = $this -> appCore -> makeSalt( 8, false);
		
		// Send user a cookie
		$this -> appCore -> setCookie( 'session_id', $this -> session_id);
		
		// Perform insert
		$this -> appCore -> db -> insert( array(
			'session_id'				=> $this -> session_id,
			'user_id' 					=> (int) $user_id,
			'session_start' 			=> $this -> appCore -> time,
			'session_last_active' 		=> $this -> appCore -> time,
			'session_ip' 				=> $this -> appCore -> db -> strEscape( $this -> appCore -> client_ip),
			'session_agent' 			=> $this -> appCore -> db -> strEscape( $this -> appCore -> client_agent),
			'session_key'				=> $this -> session_key,
			'session_code'				=> $this -> session_code
		), 'sessions');

		// Grab Session
		$this -> getSession();
		
		// Recount sessions
		class_cache::flushCache('sessions');
		
	}
	
	// ------------------------------------------------------------------------

	/**
	 * Updates User Session
	 *
	 * @param integer $user_id
	 * @param bool $hidden
	 */
	
	function updateSession( $user_id = -1, $hidden = false){
		
		// Update User ID in session
		$this -> appCore -> db -> update( array(
			'user_id' 			=> (int) $user_id,
			'session_hidden' 	=> (bool) $this -> appCore -> conf['session_allow_hide'] && $hidden
		), 'sessions', 'session_id = \'' . $this -> appCore -> db -> strEscape( $this -> session_id) . '\'');

	}
	
	// ------------------------------------------------------------------------

	/**
	 * Closes User Session
	 *
	 * @param integer $user_id
	 * @param bool $hidden
	 */
	
	function closeSession(){
		
		// Are we logged in?
		if ( $this -> appCore -> user['id'] > 0)
		{
			// Change session to Guest one
			$this -> appCore -> db -> update( array(
				'user_id' 			=> -1,
				'session_hidden' 	=> 0,
			), 'sessions', 'session_id = \'' . $this -> appCore -> db -> strEscape( $this -> session_id) . '\'');
	
			// Update last visit
			$this -> appCore -> db -> update( array(
				'user_last_date'	=> $this -> appCore -> time,
				'user_last_ip'		=> $this -> appCore -> db -> strEscape( $this -> appCore -> client_ip)
			), 'users', 'user_id = \'' . $this -> appCore -> user['id'] . '\'');
			
			// Kill remember me cookie
			$this -> appCore -> killCookie( 'session_key');
			
			// Recount sessions
			class_cache::flushCache('sessions');
			
		}
		
	}
	
	// ------------------------------------------------------------------------

	/**
	 * Start administrator session
	 *
	 * @param integer $user_id
	 */
	
	function newAdminSession( $user_id = -1){
		
		// Only if user id is correct
		if ( $user_id > 0)
		{
			// Generate session id
			$this -> session_id = md5( mt_rand( 0, 10) . time() . mt_rand( 0, 10) . $this -> appCore -> client_ip . mt_rand( 0, 10));
			
			// Generate session key and code
			$this -> session_key = $this -> appCore -> makeSalt( 6, false);
			$this -> session_code = $this -> appCore -> makeSalt( 8, false);
			
			// Send user a cookie
			$this -> appCore -> setCookie( 'adm_session_id', $this -> session_id);
			
			// Perform insert
			$this -> appCore -> db -> insert( array(
				'session_id'				=> $this -> session_id,
				'user_id' 					=> (int) $user_id,
				'session_start' 			=> $this -> appCore -> time,
				'session_last_active' 		=> $this -> appCore -> time,
				'session_ip' 				=> $this -> appCore -> db -> strEscape( $this -> appCore -> client_ip),
				'session_agent' 			=> $this -> appCore -> db -> strEscape( $this -> appCore -> client_agent),
				'session_key'				=> $this -> session_key,
				'session_code'				=> $this -> session_code
			), 'sessions_admin');
		}
		
	}
	
	// ------------------------------------------------------------------------

	/**
	 * Updates admin session
	 *
	 * @param bool $with_db
	 */
	
	function updateAdminSession(){
		
		// Perform update
		$this -> appCore -> db -> update( array(
			'session_last_active' 		=> $this -> appCore -> time,
			'session_location' 			=> $this -> appCore -> output -> action
		), 'sessions_admin', 'session_id = \'' . $this -> session_id . '\'');
		
		// Refresh cookie
		$this -> appCore -> setCookie( 'adm_session_id', $this -> session_id);
		
	}
	
	// ------------------------------------------------------------------------

	/**
	 * Kills admin session
	 *
	 * @param bool $with_db
	 */
	
	function destroyAdminSession( $with_db = false){
		
		// Set some basic data in main clas
		$this -> appCore -> user = array(
			'id' => -1,
			'language' => $this -> appCore -> conf['default_language']
		);
		
		// Kill cookie with id
		$this -> appCore -> killCookie( 'adm_session_id');
		
		// Delete session in database?
		if ( $with_db && isset( $this -> session_id[0]))
		{
			$this -> appCore -> db -> delete( 'sessions_admin', 'session_id = \'' . $this -> session_id . '\'');
		}
		
	}
	
	// ------------------------------------------------------------------------

	/**
	 * Loads Bot Session
	 *
	 */
	
	function getBotSession(){
		
		// Select Bot by ID and IP
		$this -> appCore -> db -> query( 'SELECT s.*, u.user_language, u.user_style, c.crawler_language, c.crawler_style, i.imageset_id
		FROM ' . DB_PREFIX . 'sessions s
		LEFT JOIN ' . DB_PREFIX . 'crawlers c ON s.crawler_id = c.crawler_id
		LEFT JOIN ' . DB_PREFIX . 'styles i ON c.crawler_style = i.style_id
		WHERE c.crawler_id = \'' . $this -> session_bot . '\' AND s.session_ip = \'' . $this -> appCore -> db -> strEscape( $this -> appCore -> client_ip) . '\' AND s.session_last_active >= \'' . ( $this -> appCore -> time - $this -> appCore -> conf['session_auth_timeout']) . '\'
		LIMIT 1');
		
		// Session found?
		if ( $session_result = $this -> appCore -> db -> fetch_assoc())
		{
			// Bot found?
			if ( isset( $session_result['crawler_language'][0]))
			{
				// Store whole data stuff
				
				
				// Perform update
				$this -> appCore -> db -> update( array(
					'session_last_active' 		=> $this -> appCore -> time,
					'session_action' 			=> $this -> appCore -> db -> strEscape( $this -> appCore -> _REQ( 'db')),
					'session_forum' 			=> ( !is_array( $this -> appCore -> _REQ( 'forum')) ? (integer) $this -> appCore -> _REQ( 'forum') : 0),
					'session_topic' 			=> ( !is_array( $this -> appCore -> _REQ( 'topic')) ? (integer) $this -> appCore -> _REQ( 'topic') : 0),
					'session_user' 				=> ( !is_array( $this -> appCore -> _REQ( 'user')) ? (integer) $this -> appCore -> _REQ( 'user') : 0)
				), 'sessions', 'crawler_id = \'' . $this -> session_bot . '\' AND session_ip = \'' . $this -> appCore -> db -> strEscape( $this -> appCore -> client_ip) . '\'');
			}
			else
			{
				// Bot not found. That means cache and DB are out of synch, kill cache
				class_cache::flushCache( 'crawlers');
				
				// Make new Guest session
				$this -> newSession( -1);
			}
		}
		else
		{
			// Bot Session not found, start new one
			$this -> newBotSession();
		}
		
	}
	
	// ------------------------------------------------------------------------

	/**
	 * Creates Bot Session
	 *
	 */
	
	function newBotSession(){
		
		// Generate session id
		$this -> session_id = md5( mt_rand( 0, 10) . time() . mt_rand( 0, 10) . $this -> appCore -> client_ip . mt_rand( 0, 10));
		
		// Perform insert
		$this -> appCore -> db -> insert( array(
			'session_id'				=> $this -> session_id,
			'user_id' 					=> -1,
			'crawler_id' 				=> $this -> session_bot,
			'session_start' 			=> $this -> appCore -> time,
			'session_last_active' 		=> $this -> appCore -> time,
			'session_ip' 				=> $this -> appCore -> db -> strEscape( $this -> appCore -> client_ip),
			'session_agent' 			=> $this -> appCore -> db -> strEscape( $this -> appCore -> client_agent)
		), 'sessions_admin');
			
	}
	
}